use std::{cmp::min, collections::HashMap};

use itertools::Itertools;
use rbatis::Page;
use serde::{Deserialize, Serialize};
use tower_cookies::Cookies;
use validator::ValidateLength;

use crate::{
    config::AppContext,
    domain::{class::Class, reply::Reply, setting::Setting, topic::Topic, widget::Widget},
    service::{setting_service, topic_service},
    TOKEN_NAME,
};

pub struct BaseData {
    pub setting: Setting,
    pub widget: Vec<Widget>,
    pub hot_topic: Vec<Topic>,
    pub new_reply: Vec<Reply>,
    pub tag: Vec<String>,
    pub kw: Option<String>,
    pub t: Option<String>,
    pub c: Option<u32>,
    pub webmenu: Option<String>,
    pub webclass: Vec<Class>,
    pub class: Option<String>,
    pub login: bool,
}

pub async fn base(cookies: Cookies) -> BaseData {
    let executor = AppContext::rbatis();
    let setting = setting_service::get().await;
    let mut tag: Vec<String> = Vec::new();
    let mut hot_topic: Vec<Topic> = Vec::new();
    let mut new_reply: Vec<Reply> = Vec::new();
    let widget = Widget::select_all_ord(executor).await.unwrap();
    let wid_map: HashMap<String, Vec<&Widget>> = widget
        .iter()
        .filter(|f| f.html.length() < Some(20))
        .into_group_map_by(|f| f.html.clone().unwrap_or_default());
    //查询tags
    if wid_map.contains_key("tag") {
        tag = topic_service::tags().await;
    }
    //热评文章
    if wid_map.contains_key("topic") {
        hot_topic = Topic::select_hots(executor).await.unwrap();
    }
    //最新回复
    if wid_map.contains_key("reply") {
        new_reply = Reply::select_latest(executor).await.unwrap();
    }
    //菜单
    let webclass: Vec<Class> = Class::select_by_ord(executor).await.unwrap();
    let webmenu = webmenu(&setting, &webclass);
    let class = class(&webclass);
    let login = cookies.get(TOKEN_NAME).map_or(false, |_| true);
    BaseData {
        setting,
        widget,
        hot_topic,
        new_reply,
        tag,
        webmenu,
        webclass,
        class,
        kw: None,
        t: None,
        c: None,
        login,
    }
}

fn webmenu(setting: &Setting, cls: &Vec<Class>) -> Option<String> {
    let webmenu = setting.webmenu.as_ref();
    webmenu
        .filter(|f| f.as_str().contains("@class"))
        .and_then(|_| class(cls))
        .and_then(|c| webmenu.and_then(|m| Some(m.replace("@class", c.as_str()))))
}

fn class(cls: &Vec<Class>) -> Option<String> {
    let mut class_html = String::new();
    for f in cls {
        let id = f.id.unwrap();
        let class = f.class.to_owned().unwrap();
        let html = format!(
            "<li><a id='menu-{}' href='/index?c={}'>{}</a></li>",
            id, id, class
        );
        class_html.push_str(&html);
    }
    Some(class_html)
}

#[derive(Clone, Serialize, Deserialize)]
pub struct Pagination<T: Send + Sync> {
    pub cur_page: u64,
    pub pre_page: u64,
    pub next_page: u64,
    pub total_page: u64,
    pub pages: Vec<u64>,
    /// data
    pub records: Vec<T>,
    /// total num
    pub total: u64,
    /// current page index
    pub page_no: u64,
    /// default 10
    pub page_size: u64,
}

impl<T: Send + Sync> Pagination<T> {
    pub fn new(page: Page<T>) -> Self {
        let mut ret = Pagination {
            cur_page: 0,
            pre_page: 0,
            next_page: 0,
            total_page: page.total.div_ceil(page.page_size),
            pages: Vec::default(),
            records: page.records,
            total: page.total,
            page_no: page.page_no,
            page_size: page.page_size,
        };
        let total_page = ret.total_page;

        if page.total == 0 || page.page_size == 0 {
            return ret;
        }
        if total_page == 1 {
            return ret;
        }
        let mut cur_page = page.page_no;
        let mut pre_page = cur_page - 1;
        let mut next_page = cur_page + 1;
        if cur_page >= total_page {
            cur_page = total_page;
            next_page = total_page;
        }
        if cur_page <= 1 {
            cur_page = 1;
            pre_page = 1;
        }
        ret.cur_page = cur_page;
        ret.pre_page = pre_page;
        ret.next_page = next_page;

        let num_links = 2;
        let show_nums = num_links * 2 + 1;
        if total_page <= show_nums {
            ret.pages = (1..total_page + 1).collect();
        } else {
            if cur_page < (1 + num_links) {
                ret.pages = (1..show_nums + 1).collect();
            } else if cur_page >= (total_page - num_links) {
                ret.pages = (total_page - show_nums + 1..total_page + 1).collect();
            } else {
                let start_page = cur_page - num_links;
                let end_page = min(cur_page + num_links, total_page);
                ret.pages = (start_page..end_page + 1).collect();
            }
        }
        ret
    }
}
